/**
 * Copyright 2000 self.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 *
 * @author Ashin Wimalajeewa
 */
package self.amigo.editor;

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import self.swing.*;
import self.gee.*;
import self.gee.editor.*;

import self.amigo.editor.cmd.*;
import self.amigo.editor.layer.*;

public class SelectionTool extends AEditingTool {
  final static private int UNSELECTED = 0;
  final static private int SELECTED = -1;
  final static private int MOVING = 1;
  final static private int LASSOING = 2;
  final static private int RESIZING = 3;
  
  private int                   state = UNSELECTED;
  private boolean               dragging = false;
  private int                   dragState = IGraphicElement.DS_NONE;
  
  private ArrayList             selection = new ArrayList( 50 );
  private ArrayList             removerPoints = new ArrayList();
  private GrabHandlesLayer      handleLayer = new GrabHandlesLayer();
  private LassoLayer            lassoLayer = new LassoLayer();
  private IGraphicElement       mousePressedElem;
  
  private Point                 lastDraggedPosition = new Point();
  private Point[]               handleGrabbed = new Point[1];
  private Dimension             offset = new Dimension();
  
  private SelectionMovement     moveCmd;
  private ElementResize         resizeCmd;
  
  public SelectionTool() {
    userInterfaceSink.putValue( Action.NAME, "Select" );
    Icon gif = IconUtils.getIconWithDescription("/self/amigo/images/Select.gif", "Selection Tool");
    userInterfaceSink.putValue( Action.SMALL_ICON, gif );
  }

//  private void debugState( String sMsg ) {
//    self.util.Debug.out("[" + sMsg + "]" + " state = " + state + ", dragging = " + dragging );
//  }
//    
  public void mousePressed( MouseEvent e ) {
    int sizeAtPress = selection.size();
    dragging = false;
    dragState = IGraphicElement.DS_FIRST;
    checkResize( e, sizeAtPress );
    if ( state != RESIZING ) 
      checkIfAddToSelection( e, sizeAtPress );
    e.consume();
  }
 
  public void mouseReleased( MouseEvent e ) {
    if ( dragging ) {
      int x = e.getX(), y = e.getY();
      dragState = IGraphicElement.DS_AFTER_LAST;
      switch (state) {
        case MOVING :
          moveSelections( x, y, e );
          updateHandlePoints();
          state = SELECTED;
          break;
        case RESIZING:
          resizeSelection( x, y, e );
          updateHandlePoints();
          state = SELECTED;
          break;
        case LASSOING:
          state = updateSelectionFromLasso();
          break;
      }
      handleLayer.setVisible( true );
      editor.getSource().redraw();
    }
    e.consume();
    editor.inspectSelectedObjects( selection );
//    dumpSelection();
  }
 
  public void mouseDragged( MouseEvent e ) {
    int x = e.getX(), y = e.getY();
    if ( !dragging ) {
      if ( (x == controller.touchDown.x) && (y == controller.touchDown.y) )
        return;
      dragging = true;
      handleLayer.setVisible( false );
      lastDraggedPosition.setLocation( controller.touchDown );
    }
    
    switch ( state ) {
      case RESIZING:    resizeSelection( x, y, e ); break;
      case UNSELECTED:  state = LASSOING; 
                        setLassoLayer( true );  
                        break;
      case SELECTED:    state = MOVING;   break;
    }

    switch ( state ) {
      case LASSOING:    lassoElements( x, y );  break;
      case MOVING:      moveSelections( x, y, e ); break;
    }
    lastDraggedPosition.setLocation( x, y );
    editor.getSource().redraw();
    e.consume();
    if ( dragState == IGraphicElement.DS_FIRST )
      dragState = IGraphicElement.DS_AFTER_FIRST;
  }
 
  public ILayer getGrabHandleLayer() {
    return handleLayer;
  }
  
  public void getSelectedItems( java.util.List store ) {
    store.addAll( selection );
  }
  
  public void clearSelection() {
    selection.clear();
    handleLayer.clear();
  }
  
  public void setSelectedItems( java.util.List elems ) {
    clearSelection();
    int max = elems.size();
    for ( int cntr = 0; cntr < max; cntr++ )
      addToSelection( (IGraphicElement) elems.get(cntr) );
    if ( max == 1 )
      mousePressedElem = (IGraphicElement) elems.get( 0 );
    else
      mousePressedElem = null;
  }
  
  private void checkIfAddToSelection( MouseEvent e, int sizeAtPress ) {
    boolean altered = false;
    mousePressedElem = getSelectedItem();
    if ( mousePressedElem == null ) {
      if ( sizeAtPress > 0 ) {
        clearSelection();
        altered = true;
      }
      state = UNSELECTED;
    } else {
      boolean shift = e.isShiftDown();
      boolean alreadySelected = isItemAlreadySelected(mousePressedElem);
      if ( !alreadySelected ) {
        if ( !shift ) {
          clearSelection();
        }
        addToSelection( mousePressedElem );
        altered = true;
//        self.util.Debug.out( mousePressedElem );
      } else {
        if ( (sizeAtPress > 1) && shift ) {
          removeFromSelection( mousePressedElem );
          altered = true;
        }
      }
      state = SELECTED;
    }
    if ( altered ) 
      editor.getSource().redraw();
  }
  
  private void checkResize( MouseEvent e, int sizeAtPress ) {
    if ( sizeAtPress != 1 )
      return;
      
    boolean resize = handleLayer.wasHandleGrabbed( controller.touchDown, handleGrabbed );
    if ( resize ) {
      state = RESIZING;
      offset.width = controller.touchDown.x - handleGrabbed[0].x;
      offset.height = controller.touchDown.y - handleGrabbed[0].y;
    }
  }
  
  private void moveSelections( int x, int y, MouseEvent e ) {
    if ( dragState == IGraphicElement.DS_FIRST ) {
      moveCmd = new SelectionMovement( editor, controller, e, selection );
    } else if ( dragState == IGraphicElement.DS_AFTER_LAST ) {
      moveCmd.endMovement( e );
      editor.executeCommand( ICommand.ET_PRE_EXECUTED, moveCmd );
    }
    
    offset.width = x - lastDraggedPosition.x;
    offset.height = y - lastDraggedPosition.y;
    
    controller.diagramLayer.beginUpdate();
    int max = selection.size();
    for ( int cntr = 0; cntr < max; cntr++ ) {
      IGraphicElement elem = (IGraphicElement) selection.get( cntr );
      elem.move( offset, e, dragState );
    }
    controller.diagramLayer.endUpdate();
  }
  
  private void resizeSelection( int x, int y, MouseEvent e ) {
    if ( mousePressedElem == null )
      return;
    if ( dragState == IGraphicElement.DS_FIRST ) {
      resizeCmd = new ElementResize( editor, controller, mousePressedElem );
    } else if ( dragState == IGraphicElement.DS_AFTER_LAST ) {
      resizeCmd.endMovement();
      editor.executeCommand( ICommand.ET_PRE_EXECUTED, resizeCmd );
    }

    x -= offset.width;
    y -= offset.height;
    
    controller.diagramLayer.beginUpdate();
    mousePressedElem.resize( x, y, handleGrabbed, e, dragState );
    controller.diagramLayer.endUpdate();
  }
  
  private void lassoElements( int x, int y ) {
    lassoLayer.setLassoTo( x, y );
  }
  
  private void updateHandlePoints() {
    handleLayer.clear();
    int max = selection.size();
    for ( int cntr = 0; cntr < max; cntr++ ) {
      IGraphicElement elem = (IGraphicElement) selection.get( cntr );
      elem.getHandlePoints( handleLayer.pointHandles );
    }
  }
  
  private int updateSelectionFromLasso() {
    lassoLayer.clear();
    Rectangle lassoRegion = lassoLayer.getLassooRegion();
    setLassoLayer( false );  
    controller.diagramLayer.getElementWithinLasso( lassoRegion, selection );
    updateHandlePoints();
    return selection.size() == 0 ? UNSELECTED: SELECTED;
  }
 
  private void setLassoLayer( boolean showLasso ) {
    ILayer foreground = showLasso ? (ILayer) lassoLayer: (ILayer) handleLayer;
    editor.getSource().setForeground( foreground );
    if ( showLasso )
      lassoLayer.anchor = controller.touchDown;
  }
  
  private void addToSelection( IGraphicElement elem ) {
    selection.add( elem );
    elem.getHandlePoints( handleLayer.pointHandles );
  }
 
  private void removeFromSelection( IGraphicElement elem ) {
    removerPoints.clear();
    elem.getHandlePoints( removerPoints );
    int max = removerPoints.size();
    for ( int cntr = 0; cntr < max; cntr++ ) {
      handleLayer.pointHandles.remove( removerPoints.get(cntr) );
    }
    selection.remove( elem );
  }
  
  private boolean isItemAlreadySelected( IGraphicElement elem ) {
    return ( selection.indexOf(elem) >= 0 );
  }
  
  private IGraphicElement getSelectedItem() {
    return controller.diagramLayer.getElementAtPoint( controller.touchDown );
  }
}
